package org.budo.dubbo.protocol.http.invocation.adapter.impl;

import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.budo.dubbo.protocol.http.Consts;
import org.budo.dubbo.protocol.http.dispatcher.servlet.ServletProtocolRequest;
import org.budo.dubbo.protocol.http.exception.remoting.InterfaceMissMatchException;
import org.budo.dubbo.protocol.http.exception.remoting.MethodMissMatchException;
import org.budo.dubbo.protocol.http.exception.remoting.RemotingRuntimeException;
import org.budo.dubbo.protocol.http.invocation.BudoDubboRpcInvocation;
import org.budo.dubbo.protocol.http.invocation.adapter.AbstractInvocationAdapter;
import org.budo.dubbo.protocol.http.invocation.adapter.ProtocolRequest;
import org.budo.dubbo.protocol.http.util.BudoReflectionUtil;
import org.budo.dubbo.protocol.http.util.BudoSerializationUtil;
import org.budo.support.lang.util.ReflectUtil;
import org.springframework.web.bind.annotation.ResponseBody;

import com.alibaba.dubbo.common.serialize.Serialization;
import com.alibaba.dubbo.common.utils.StringUtils;
import com.alibaba.fastjson.JSON;

/**
 * 上传的参数是一个json对象，key-value方式
 * 
 * @author lmw
 */
public class WebHttpInvocationAdapter extends AbstractInvocationAdapter {
    @Override
    public BudoDubboRpcInvocation adaptInvocation(ProtocolRequest protocolRequest) throws Throwable {
        ServletProtocolRequest servletRequest = (ServletProtocolRequest) protocolRequest;
        HttpServletRequest httpRequest = servletRequest.getRequest();

        String requestUri = httpRequest.getRequestURI();
        boolean IS_POST_REQUEST = "POST".equalsIgnoreCase(httpRequest.getMethod());

        // 解析
        String[] typeAndMethod = ServletProtocolRequest.parseUrlToTypeAndMethod(protocolRequest);
        if (null == typeAndMethod) {
            return null;
        }

        String interfaceNameInUrl = typeAndMethod[0];
        String methodName = typeAndMethod[1];

        // 查找接口
        String matchedInterfaceName = BudoReflectionUtil.getMatchedInterfaceNameCached(interfaceNameInUrl); // 已缓存
        if (StringUtils.isEmpty(matchedInterfaceName)) {
            if (!IS_POST_REQUEST) {
                return null;
            }

            throw new InterfaceMissMatchException(requestUri, interfaceNameInUrl);
        }

        // 查找方法
        Class<?> matchedInterface = ReflectUtil.classForName(matchedInterfaceName);
        Method matchedMethod = BudoReflectionUtil.getMatchedMethodCached(matchedInterfaceName, methodName); // 已缓存

        if (null == matchedMethod) {
            if (!IS_POST_REQUEST) {
                return null;
            }

            throw new MethodMissMatchException(requestUri, interfaceNameInUrl, methodName);
        }

        Class<?>[] parameterTypes = matchedMethod.getParameterTypes();

        // 组装
        BudoDubboRpcInvocation budoDubboRpcInvocation = new BudoDubboRpcInvocation();
        budoDubboRpcInvocation.setMethodName(methodName);
        budoDubboRpcInvocation.setParameterTypes(parameterTypes);
        budoDubboRpcInvocation.setInterfaceName(matchedInterfaceName);

        // 后面要用
        httpRequest.setAttribute(Consts.HeaderKey.InterfaceName, matchedInterfaceName);
        httpRequest.setAttribute(Consts.HeaderKey.MethodName, methodName);

        String requestBody = this.getProtocolRequestBody(protocolRequest);
        budoDubboRpcInvocation.setRequestBody(requestBody);
        Object[] argumentArray = this.argumentArray(protocolRequest, requestBody, matchedMethod, matchedInterface);
        budoDubboRpcInvocation.setArguments(argumentArray);

        // @ResponseBody
        budoDubboRpcInvocation.setResponseBody(null != matchedMethod.getAnnotation(ResponseBody.class));

        super.readAttachments(protocolRequest, budoDubboRpcInvocation);

        return budoDubboRpcInvocation;
    }

    /**
     * 可能会有GET请求的情况
     */
    @Override
    public String getProtocolRequestBody(ProtocolRequest protocolRequest) throws IOException {
        HttpServletRequest request = ((ServletProtocolRequest) protocolRequest).getRequest();

        boolean IS_GET_REQUEST = "GET".equalsIgnoreCase(request.getMethod());
        if (IS_GET_REQUEST) { // GET 请求 parameterMap 传参
            return JSON.toJSONString(request.getParameterMap()); // TODO 性能问题
        }

        return super.getProtocolRequestBody(protocolRequest);
    }

    /**
     * 返回参数数组
     */
    public Object[] argumentArray(ProtocolRequest protocolRequest, String requestBody, Method matchedMethod, Class<?> matchedInterface) {
        Map argumentMap = null;
        if (null == requestBody || requestBody.isEmpty()) { // 前端传入空
            argumentMap = null;
        } else {
            argumentMap = (Map) this.deserializeRequestBody(protocolRequest, requestBody, matchedMethod, Map.class);
        }

        Class<?>[] parameterTypes = matchedMethod.getParameterTypes();
        String[] parameterNames = BudoReflectionUtil.getParameterNamesCached(matchedInterface, matchedMethod); // 已缓存

        Object[] argumentArray = new Object[parameterTypes.length];
        for (int index = 0; index < argumentArray.length; index++) {
            String parameterName = parameterNames[index];
            Object argumentValue = null == argumentMap ? null : argumentMap.get(parameterName);

            Class<?> parameterType = parameterTypes[index];
            argumentArray[index] = this.parseArgumentValue(matchedInterface, matchedMethod, index, parameterType, argumentValue);
        }
        return argumentArray;
    }

    protected Object deserializeRequestBody(ProtocolRequest protocolRequest, String requestBody, Method matchedMethod, Class<?> type) {
        Serialization serialization = null;
        try {
            serialization = BudoSerializationUtil.getSerialization(protocolRequest);
            byte[] requestBytes = requestBody.getBytes(Consts.UTF_8);
            return BudoSerializationUtil.deserialize(serialization, requestBytes, type);
        } catch (Throwable e) {
            String message = "#159 deserializeRequestBody error, serialization=" + serialization //
                    + ", protocolRequest=" + protocolRequest //
                    + ", requestBody=" + requestBody //
                    + ", matchedMethod=" + matchedMethod;
            throw new RemotingRuntimeException(message, e);
        }
    }
}
